feat(reporting): negotiated contracts for automatic breakdowns - #6627
Conversation
quartile_25..quartile_100 and the viewability leaves (viewable_rate, viewable_impressions, measurable_impressions, viewed_seconds) become first-class metric identities in available-metric and sort-metric, resolving to their nested canonical values with no duplicate flat fields. Closes the existing contradiction where committed-metric qualifier rules and the delivery-metric-aggregate viewable_rate conditional referenced unrepresentable metric_ids and shipped examples failed their own schemas. Also restores sort-metric's claimed coverage of flat numeric delivery metrics (cpm, cost_per_completed_view, downloads, units_sold, new_to_brand_units, plays, commissionable_value), with lift scalars documented as intentionally sort-excluded. Adds a metric-identity coherence contract test enforcing enum/schema/example agreement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Breakdown sorting was descending-only with a silent fallback to spend when the seller does not report the requested metric — an agentic buyer asking for top geos by completion_rate gets spend-ordered rows and reasons over them as completion-ordered. Add sort_direction (asc/desc, default desc) to the six sortable dimensions, enabling bottom-N queries that cannot be recovered from a truncated descending pull, and a per-breakdown applied-sort echo (by_X_sorted_by / by_X_sort_direction, MUST whenever the breakdown is present) so the fallback is visible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reporting webhook can narrow which metrics are delivered (reporting_webhook.requested_metrics); the GET path could not, so the interactive surface where agent context budget matters most always paid full payload cost. Add the same narrowing to the request: omitted means unchanged full payloads; impressions and spend are always included; requesting a leaf metric identity returns its canonical nested carrier; and missing_metrics MUST NOT flag absences caused solely by request narrowing. Implements RFC #6624. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Leaf identities split the metric vocabulary into container and leaf tokens, which made token-equality set operations ambiguous: a product declaring viewability could be silently excluded by a required_metrics filter for viewable_rate, and a format/product intersection of mixed declarations could yield the empty set. State the rule once in available-metric (container subsumes its leaves; leaf selects its carrier; a leaf implies neither siblings nor the carrier's other fields) and cite it from required_metrics, reported_metrics, and requested_metrics. Also: sellers MUST populate viewability.standard when the commitment carries a viewability_standard qualifier, and the leaf-identity docs paragraph no longer splits the metric bullet list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings: the six sort_by descriptions still claimed descending- only next to the new sort_direction field; the spend fallback had no grain (package vs row) so two conformant sellers could return disjoint top-N sets; rows lacking the sort metric had undefined order (NULLS FIRST vs LAST) exactly where bottom-N queries live; and direction had no honor rule, letting a fallback gut the asc use case. Canonical rule now at every site: fallback triggers only when the metric is not reported at the breakdown's row grain, direction resets to desc on metric fallback and is otherwise a MUST, rows lacking the applied metric order last. Sortable-dimension invariants are now derived from the schema instead of a hardcoded list, so a future sortable dimension cannot ship without the echo contract. Also fixes the stale descending claim in managing-response-size.mdx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # docs/media-buy/media-buys/optimization-reporting.mdx
by_creative, by_keyword, and by_catalog_item were returned automatically with no limit, sort, or truncation disclosure, so "top creatives by quartile_100" had no completeness contract. Including their keys in reporting_dimensions now upgrades them to the negotiated contract: limit/sort_by/sort_direction honored, by_X_truncated and the applied-sort echo become binding. Omitting the keys preserves the automatic behavior exactly — the MUSTs are scoped to request-key presence, so existing automatic emissions stay conformant. The new dimensions carry the full deterministic sort semantics (row-grain fallback, nulls-last, direction MUST). Implements RFC #6623. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema paths require human/CODEOWNERS approval.
This PR modifies two published protocol schema files under static/schemas/source/**:
get-media-buy-delivery-request.jsonget-media-buy-delivery-response.json
gated_paths is true and review_decision is not APPROVED (unknown/none), so the hard approval gate (decision rule row 2) fires: the outcome must be escalate, not approve.
The change itself looks clean per the reviewer: an additive, backward-compatible minor (RFC #6623) making automatic creative/keyword/catalog_item breakdowns optionally negotiable via reporting_dimensions, with a dual MUST/RECOMMENDED contract keyed to request-key presence. Schema↔docs coherence holds, reporting_dimensions is additionalProperties:true, echo/truncation MUSTs bind to request-key presence, and a correct minor changeset is present. No blocking or Medium findings were raised.
Nonetheless, the gated schema paths require an explicit human/CODEOWNERS approval before merge. Once review_decision becomes APPROVED, this PR can fall through to a normal approve on its clean diff.
Why human review
- Modifies gated schema file static/schemas/source/media-buy/get-media-buy-delivery-request.json — human/CODEOWNERS review required (review_decision is not APPROVED)
- Modifies gated schema file static/schemas/source/media-buy/get-media-buy-delivery-response.json — human/CODEOWNERS review required (review_decision is not APPROVED)
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/media-buy/get-media-buy-delivery-request.json (modified) matches
static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
Ladon review: "same semantics as reporting_webhook.requested_metrics" was not literally true — the GET field adds minItems: 1 and the always-included impressions/spend rule was stated only on the GET side. State the impressions/spend rule on the webhook field too, define the webhook's empty-array case as equivalent to omission, and describe the one intentional shape difference explicitly on both sides. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema files touched without a recorded APPROVED review.
This PR modifies two files under the hard, non-overridable approval gate static/schemas/source/**:
static/schemas/source/media-buy/get-media-buy-delivery-request.jsonstatic/schemas/source/media-buy/get-media-buy-delivery-response.json
gated_paths is true and review_decision is not APPROVED (unknown/none). Per decision-table row 2, the outcome must be escalate until a human/CODEOWNERS approval is recorded. This is a mechanical, path-based gate — a clean diff cannot lift it.
Findings: none. The reviewer confirms the delta since the prior clean pass is description-only refinements to requested_metrics cross-referencing reporting-webhook.json and the delivery-request field. Descriptions match their schemas and each other; no wire-shape, required[], or enum changes; nothing blocking or Medium.
Escalation reasons: hard gated-path approval requirement, pending human review. Once review_decision becomes APPROVED, this clean diff falls through to approve.
Why human review
- Modifies gated schema file static/schemas/source/media-buy/get-media-buy-delivery-request.json — human/CODEOWNERS review required (review_decision is not APPROVED)
- Modifies gated schema file static/schemas/source/media-buy/get-media-buy-delivery-response.json — human/CODEOWNERS review required (review_decision is not APPROVED)
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/media-buy/get-media-buy-delivery-request.json (modified) matches
static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
…face The empty-array-equals-omission rule was stated only on the GET request's description; a reader of reporting-webhook.json alone could derive empty = impressions+spend only. State it where it applies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verification review: two task-reference sentences stated unconditional
MUSTs ("whenever the corresponding breakdown array is present") that
the schema correctly scopes to request-key presence — the unconditional
form would retroactively bind automatic creative/keyword/catalog_item
emissions. Docs now match the schema contract.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated schema paths modified without an APPROVED review decision.
This PR modifies two files under static/schemas/source/**, which sit behind a hard, deterministic approval gate (gated_paths: true). The current review_decision is unknown/none, not APPROVED, so the gate is not satisfied and Ladon cannot auto-approve regardless of how clean the diff is (decision rule row 2).
The reviewer found no critical/high/medium issues: the delta since the prior clean pass is two prose/description clarifications. get_media_buy_delivery.mdx scopes the echo/truncation MUST to requested dimensions and marks automatic creative/keyword/catalog_item fields RECOMMENDED, coherent with the response schema verbatim. The webhook requested_metrics empty==omitted definition agrees with the get_media_buy_delivery cross-reference. No schema drift, no wire-shape change, changeset present. This mirrors the prior escalate, which persists here on the gate alone (not sticky row 6 — there are no fresh findings, but row 2 fires independently and deterministically).
Escalation reasons
- Modifies gated schema file
static/schemas/source/media-buy/get-media-buy-delivery-request.json— human/CODEOWNERS review required (review_decision not APPROVED). - Modifies gated schema file
static/schemas/source/media-buy/get-media-buy-delivery-response.json— human/CODEOWNERS review required (review_decision not APPROVED).
Once a human/CODEOWNERS approval is recorded, this PR can fall through to a normal approve on its clean diff.
Why human review
- Modifies gated schema file static/schemas/source/media-buy/get-media-buy-delivery-request.json under static/schemas/source/** — human/CODEOWNERS review required because review_decision is not APPROVED
- Modifies gated schema file static/schemas/source/media-buy/get-media-buy-delivery-response.json under static/schemas/source/** — human/CODEOWNERS review required because review_decision is not APPROVED
- This PR touches a path under a hard, non-overridable approval gate (static/schemas/source/media-buy/get-media-buy-delivery-request.json (modified) matches
static/schemas/source/**; static/schemas/source/media-buy/get-media-buy-delivery-response.json (modified) matchesstatic/schemas/source/**) and the current GitHub review decision is 'unknown', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
…owns # Conflicts: # .changeset/metric-leaf-identities.md # docs/media-buy/media-buys/optimization-reporting.mdx # docs/media-buy/task-reference/get_media_buy_delivery.mdx # package.json # static/schemas/source/enums/available-metric.json # static/schemas/source/enums/sort-metric.json
Summary
Top of the stack (#6618 → #6622 → #6626 → this; bases retarget as the stack merges). Implements RFC #6623.
by_creative,by_keyword, andby_catalog_itemwere returned automatically with nolimit,sort_by, or truncation disclosure — so "top creatives byquartile_100" (sortable as of #6618) had no completeness contract, and a seller returning 50 of 500 creative rows couldn't say which 50 or whether the list was complete.The dual contract
{}):limit/sort_by/sort_directionare honored, andby_X_truncated+ the applied-sort echo (by_X_sorted_by/by_X_sort_direction) become MUST.The per-review scoping matters: the echo/truncation MUSTs bind to request-key presence, not array presence — an unconditional MUST would have retroactively broken every existing automatic emission. Since
reporting_dimensionsisadditionalProperties: true, pre-3.2 sellers silently ignore the new keys; the applied-sort echo is therefore the capability-detection signal (its absence on a keyed request means the seller doesn't implement negotiation).The three dimensions carry the full deterministic sort semantics from #6622 (row-grain fallback, nulls-last ordering, direction MUST), and the schema-derived invariant test in the sort-contract suite automatically covers them — a sortable dimension can no longer ship without the echo contract.
Relationship: #5671 (breakdown continuation) remains the complement — sort+limit gets the top-N, continuation gets the rest; both share truncation semantics.
🤖 Generated with Claude Code